﻿using Microsoft.Playwright;
using NPOI.SS.UserModel;
using DdddOcrLib;
using NLog;
using System.Runtime.CompilerServices;
using System.Diagnostics;


namespace GKScoreQueryHelper
{
    public partial class FormMain : Form
    {
        public event TaskFinishedHandler TaskFinished;
        public event TaskCancelHandler TaskCanceled;

        private static int dataErrorCount = 0;
        private static int YZMErrorCount = 0;
        private QueryRowDelegate? queryRowDelegate;
 
        //申明时直接初始化
        private static readonly DdddOcr ocrEngine = new DdddOcr();

        //申明时直接初始化
        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
        private static volatile  bool CancelFlag = false;
        private bool finished = false;
        private bool running = false;

        //申明时直接初始化
        private static  CustomConfiguration Config = new CustomConfiguration(".\\config.toml");


        public static void InitEngine()
        {
            ocrEngine.InitDdddOcr();
        }


        public FormMain()
        {
            InitializeComponent();
            InitEngine();
            this.TaskFinished += DoTaskFinished;
            this.TaskCanceled += DoTaskCanceled;
            this.btnPause.Enabled = false;
        }


        /// <summary>
        /// 加载配置文件
        /// </summary>
 
        /// <summary>
        /// 重新加载配置文件
        /// </summary>
   


        /// <summary>
        /// 任务完成执行的操作
        /// </summary>
        /// <param name="taskInformation"></param>
        private void DoTaskFinished(TaskInformation taskInformation)
        {
            var msg = $"任务已完成，共查询任务{taskInformation.FinishedCount}个，耗时{taskInformation.SpentTime.TotalSeconds}秒!验证码识别错误{YZMErrorCount}次！";
            MessageBox.Show(msg, "任务已完成");
            logger.Info(msg);

            finished = true;  //设置完成标志为true，点击退出按钮后，会退出整个程序
            btnStart.Enabled = false;  //开始按钮不能点击
            btnPause.Enabled = true;//暂停按钮可以点击，文字变为退出。
            btnPause.Text = "退出";
            btnContinue.Enabled = false;//继续查询不能点击。

            running = false; //运行标志切换为false
        }

        /// <summary>
        /// 取消任务执行的操作
        /// </summary>
        /// <param name="ti"></param>
        private void DoTaskCanceled(TaskInformation ti)
        {
            var msg = $"任务被取消，共查询任务{ti.FinishedCount}个，耗时{ti.SpentTime.TotalSeconds}秒!验证码识别错误{YZMErrorCount}次！";
            MessageBox.Show(msg, "任务被取消");
            logger.Info(msg);

            btnStart.Enabled = true;
            //btnPause.Enabled = false;点击暂停按钮时已设置，此处不用重复设置。
            btnContinue.Enabled = true;
            running = false;
        }

        //private async Task AutoRunTaskGroup(IPage page,)
        


        /// <summary>
        /// 完成对一个文件的查询工作。
        /// </summary>
        /// <param name="page">page对象</param>
        /// <param name="templateFileName">文件位置</param>
        /// <param name="tp">任务运行参数</param>
        /// <returns></returns>
        private async Task QueryFile(IPage page, string templateFileName,TaskParameters tp)
        {
            IWorkbook wb;
            using (FileStream fs = File.OpenRead(templateFileName))
            {
                wb = WorkbookFactory.Create(fs);
            }
                
            var sh = wb.GetSheetAt(0);

            //检查是否有确实的字段，如果有缺失的字段则直接返回，不再运行后面的代码。
            var dic = sh.GetFieldsNames();
            string? deletionFields = Utils.ValideFields(Config.Format_fields, dic);
            if (!string.IsNullOrEmpty(deletionFields))
            {
                MessageBox.Show($"指定的Excel文件中未包含必需的字段：{deletionFields}", "未包含必需字段");
                return;
            }

            //确定工作总量，并设置进度条
            int row_num = sh.PhysicalNumberOfRows;
            pb.Invoke(SetupMaxium, row_num - 1);

            //以下变量是为估算剩余时间用的。
            int startTime = System.Environment.TickCount;
            int currentTime = 0, usedTime = 0, leftTime = 0;
            int successCount = 0, ignoreCount = 0;

            //以下变量都是为加速循环内运算设置的。
            int returnNameIndex = dic["返回姓名"];
            



            if (tp.CaptchaType == 1)
            {
                queryRowDelegate = QueryRowByOcr;
            }
            else
            {
                queryRowDelegate = QueryRowBySearchValidateCode;
            }

            try
            {
                for (int i = 1; i < row_num; i++)
                {
                    //如果设置了取消标志，则退出循环。
                    if (CancelFlag)
                    {
                        TaskCanceled?.Invoke(new TaskInformation { FinishedCount = successCount, SpentTime = TimeSpan.FromSeconds(usedTime) });
                        break;
                    }

                    IRow row = sh.GetRow(i);
                    //如果已做过标记，则说明该行已经查询过，直接跳过。
                    var cell = row.GetCell(returnNameIndex);
                    if (cell is not null)
                    {
                        ignoreCount++;
                    }
                    else
                    {
                        var score = await queryRowDelegate.Invoke(page, row, dic);
                        //将查询结果写入Excel缓存
                        foreach (var kv in score)
                        {
                            row.CreateCell(dic[kv.Key]).SetCellValue(kv.Value);
                        }
                        successCount++;
                    }

                    currentTime = System.Environment.TickCount;
                    usedTime = (currentTime - startTime) / 1000;
                    leftTime = usedTime * (row_num - 1) / i - usedTime;
                    pb.BeginInvoke(Increase);
                    lbProgress.BeginInvoke(UpdateProgress, $"已完成{i}个/共{row_num - 1}个,其中：查询成功{successCount} 个，跳过{ignoreCount}个，错误{dataErrorCount}个。\r\n其中验证码识别错误{YZMErrorCount}次。");
                    lbTime.BeginInvoke(UpdateTime, $"已用时{usedTime}秒,预计还剩{leftTime}秒");

                }

                //将Workbook写回文件，无论是否取消，都将已完成的查询部分写入文件。
                using (FileStream fss = new FileStream($"{Config.Work_folder}\\{Config.File_result}", FileMode.Create, FileAccess.Write))
                {
                    wb.Write(fss,false);
                    wb.Close();
                }

                //如果没有取消，说明任务正常完成，否者是用户自行取消的。
                if (!CancelFlag)
                {
                    finished = true;
                    TaskFinished?.Invoke(new TaskInformation { FinishedCount = row_num - 1, SpentTime = TimeSpan.FromSeconds(usedTime) });
                }

            }
            catch (Exception ex)
            {
                //确保已经查询完成的部分被写入文件。
                using (FileStream fsWrite = new FileStream(templateFileName, FileMode.Create, FileAccess.Write))
                {
                    wb.Write(fsWrite, false);
                }
                    


                running = false;
                logger.Error(ex, $"任务出现异常。共成功查询{successCount}个,耗时{usedTime}秒,跳过{ignoreCount}个，错误{dataErrorCount}个，其中验证码识别错误{YZMErrorCount}次。");
                MessageBox.Show($"{ex.Message}");

            }
            finally
            {
                running = false;
            }
        }

        /// <summary>
        /// 异步函数，使用识别验证码的方式完成一行的查询工作
        /// </summary>
        /// <param name="page"></param>
        /// <param name="row"></param>
        /// <param name="tableFields"></param>
        /// <returns></returns>
        public  async Task<Dictionary<string, string>> QueryRowByOcr(IPage page, IRow row, Dictionary<string, int> tableFields) 
        {
            await page.GotoAsync(Config.Server_URL);

            var testValicode=await getCaptchaFromCookie(page,Config.Server_URL);

            int xmIndex = tableFields["返回姓名"];
            string gkbmh = row.GetCell(tableFields["报名号"]).ToString() ?? "";
            string sfzh = row.GetCell(tableFields["身份证号"]).ToString() ?? "";
            string xm = row.GetCell(xmIndex)?.ToString() ?? "";
            bool dataErrored = false;

            int retryTimes = 0;
            while (retryTimes < Config.Runtime_max_retry_times && !dataErrored)
            {
                var img = await page.Locator(Config.Page_valicode).ScreenshotAsync();
                var valicode = ocrEngine.Classification(img);
                while (valicode.Length != Config.Page_yzm_length)
                {
                    await page.GetByRole(AriaRole.Img, new() { Name = Config.Page_retry }).ClickAsync();
                    img = await page.Locator(Config.Page_valicode).ScreenshotAsync();
                    valicode = ocrEngine.Classification(img);
                    YZMErrorCount++;
                }

                //await page.Locator(gkbmh_locator).ClickAsync();
                await page.Locator(Config.Page_gkbmh).FillAsync(gkbmh);

                //await page.Locator(sfzh_locator).ClickAsync();
                await page.Locator(Config.Page_sfzh).FillAsync(sfzh);

                await page.Locator(Config.Page_yzm).FillAsync(valicode);


                //点击查询并等待页面加载成功！
                await page.RunAndWaitForResponseAsync(async () =>
                {
                    await page.Locator(Config.Page_submit).ClickAsync();
                }, response => response.Status == 200);


                //说明已提交
                if (await page.IsVisibleAsync(".score-info"))
                {
                    //返回了成绩数据
                    if (await page.IsVisibleAsync(Config.Page_cjtable))
                    {
                        break;
                    }
                    else
                    {
                        await page.GetByRole(AriaRole.Link, new() { Name = Config.Page_next }).ClickAsync();
                        retryTimes++;
                        YZMErrorCount++;
                        continue;
                    }
                }
                else
                {
                    //没有提交成功，最有可能是准考证号或身份证号错误
                    dataErrored = true;
                    dataErrorCount++;
                    logger.Info($"数据错误，高考报名号:{gkbmh}，身份证号:{sfzh}");
                    //关闭对话框
                    await page.GetByText("×").ClickAsync().WaitAsync(new TimeSpan(0, 0, 2));

                    retryTimes++;
                }

            }




            Dictionary<string, string> dic = [];
            if (!dataErrored)
            {
                var sfTable = page.Locator(Config.Page_basetable);
                var returnxm = await sfTable.Locator("tr").Nth(0).Locator("td").Nth(1).InnerTextAsync();

                var table = page.Locator(Config.Page_cjtable);

                dic.Add("返回姓名", returnxm);
                var tr_root = table.Locator("tr");
                for (int i = 0; i < 7; i++)
                {
                    var tr = tr_root.Nth(i + 1);
                    dic.Add(await tr.Locator("td").First.InnerTextAsync(), await tr.Locator("td").Nth(1).InnerTextAsync());
                }
            }
            else
            {
                dic.Add("返回姓名", "数据错误");
            }


            return dic;
        }
        /// <summary>
        /// 异步函数，使用查询验证码的方式完成一行的查询工作。
        /// </summary>
        /// <param name="page"></param>
        /// <param name="row"></param>
        /// <param name="tableFields"></param>
        /// <returns></returns>
        public async Task<Dictionary<string, string>> QueryRowBySearchValidateCode(IPage page, IRow row, Dictionary<string, int> tableFields)
        {
            await page.GotoAsync(Config.Server_URL);

            var testValicode = await getCaptchaFromCookie(page,Config.Server_URL);

            int xmIndex = tableFields["返回姓名"];
            string gkbmh = row.GetCell(tableFields["报名号"]).ToString() ?? "";
            string sfzh = row.GetCell(tableFields["身份证号"]).ToString() ?? "";
            string xm = row.GetCell(xmIndex)?.ToString() ?? "";
            bool dataErrored = false;


            await page.Locator(Config.Page_gkbmh).FillAsync(gkbmh);

            //await page.Locator(sfzh_locator).ClickAsync();
            await page.Locator(Config.Page_sfzh).FillAsync(sfzh);

            await page.Locator(Config.Page_yzm).FillAsync(testValicode);


            //点击查询并等待页面加载成功！
            await page.RunAndWaitForResponseAsync(async () =>
            {
                await page.Locator(Config.Page_submit).ClickAsync();
            }, response => response.Status == 200);




            if(!await page.IsVisibleAsync(Config.Page_cjtable))
            {
                //没有提交成功，最有可能是准考证号或身份证号错误
                dataErrored = true;
                dataErrorCount++;
                logger.Info($"数据错误，高考报名号:{gkbmh}，身份证号:{sfzh}");
                //关闭对话框
                await page.GetByText("×").ClickAsync().WaitAsync(new TimeSpan(0, 0, 2));
            }


            Dictionary<string, string> dic = [];
            if (!dataErrored)
            {
                var sfTable = page.Locator(Config.Page_basetable);
                var returnxm = await sfTable.Locator("tr").Nth(0).Locator("td").Nth(1).InnerTextAsync();

                var table = page.Locator(Config.Page_cjtable);

                dic.Add("返回姓名", returnxm);
                var tr_root = table.Locator("tr");
                for (int i = 0; i < 7; i++)
                {
                    var tr = tr_root.Nth(i + 1);
                    dic.Add(await tr.Locator("td").First.InnerTextAsync(), await tr.Locator("td").Nth(1).InnerTextAsync());
                }
            }
            else
            {
                dic.Add("返回姓名", "数据错误");
            }


            return dic;
        }


        /// <summary>
        /// 更新Progress进度条函数
        /// </summary>
        private void Increase()
        {
            pb.Value++;
        }
        /// <summary>
        /// 设置进度条最大值
        /// </summary>
        /// <param name="max"></param>
        private void SetupMaxium(int max)
        {
            pb.Maximum = max;
        }

        /// <summary>
        /// 更新用时和剩余时间预测
        /// </summary>
        /// <param name="info"></param>
        private void UpdateTime(string info)
        {
            lbTime.Text = info;
        }
        
        /// <summary>
        /// 更新进度显示文字
        /// </summary>
        /// <param name="info"></param>
        private void UpdateProgress(string info)
        {
            lbProgress.Text = info;
        }


        private async void btnStart_Click(object sender, EventArgs e)
        {

            logger.Info("启动任务(从头开始）");

            running = true; //切换运行标志为true
            CancelFlag = false;//设置取消标志为false
            finished = false;//完成标志设置为false。如果已经完成，则btnStart按钮会被禁用，所以不会重复运行。

            btnStart.Enabled = false;
            btnPause.Enabled = true;
            btnContinue.Enabled = false;

            //启动任务
            var playwright = await Playwright.CreateAsync();
            var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
            {
                Headless = Config.Server_Headless,
                Channel = Config.Server_channel,
                ChromiumSandbox = Config.Server_chromiumSandbox,
            });
            var context = await browser.NewContextAsync();
            var page = await context.NewPageAsync();
            var temp = $"{Config.Work_folder}\\{Config.File_template}";
            await QueryFile(page, temp, new TaskParameters { CaptchaType = Config.Runtime_captcha_type, TaskType = Config.Runtime_task_type });

        }



        //暂停按钮可以在暂停和运行之间切换。
        private void btnPause_Click(object sender, EventArgs e)
        {
            btnPause.Enabled = false;
            if (!finished)
            {
                CancelFlag = true;
            }
            else
            {
                this.Close();
            }

        }

        private async void btnContinue_Click(object sender, EventArgs e)
        {
            running = true;
            //设置状态
            logger.Info("启动任务(继续运行）");
            CancelFlag = false;
            finished = false;

            btnStart.Enabled = false; //从头开始按钮不能用
            btnPause.Enabled = true;//暂停按钮可用
            btnContinue.Enabled = false;//继续运行按钮不能用

            //启动任务
            var playwright = await Playwright.CreateAsync();
            var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
            {
                Headless = Config.Server_Headless,
                Channel = Config.Server_channel,
                ChromiumSandbox = Config.Server_chromiumSandbox,
            });
            var context = await browser.NewContextAsync();
            var page = await context.NewPageAsync();

            var temp = $"{Config.Work_folder}\\{Config.File_result}";
            await QueryFile(page, temp, new TaskParameters { CaptchaType = Config.Runtime_captcha_type, TaskType = Config.Runtime_task_type });
        }

        private  void FormMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (running)
            {
                MessageBox.Show("当前有任务正在运行中，请停止任务后再关闭程序", "提醒", MessageBoxButtons.OK);
                e.Cancel = true;
            }
        }

        /// <summary>
        /// 从Cookie中查找验证码
        /// </summary>
        /// <param name="page"></param>
        /// <param name="url"></param>
        /// <returns></returns>
        private async Task<string> getCaptchaFromCookie(IPage page,string url)
        {
            string value = string.Empty;
            var context = page.Context;
            var cookie = await context.CookiesAsync([url]);
            foreach (var kv in cookie)
            {
                if (kv.Name == "ValidateCode")
                {
                    value = kv.Value;
                    break;
                }
            }
            return value;
        }



    }
    //任务运行信息，主要用于触发事件时返回用。
    public class TaskInformation
    {
        public TimeSpan SpentTime { get; set; }
        public int FinishedCount { get; set; }
    }


    /// <summary>
    /// 任务运行参数
    /// </summary>
    public struct TaskParameters(int taskType, int captchaType)
    {
        /// <summary>
        /// 任务类型，1-单文件模式，2-批量模式
        /// </summary>
        public int TaskType = taskType;
        /// <summary>
        /// 验证码识别模式，1-识别模式，2-批量模式
        /// </summary>
        public int CaptchaType = captchaType;

    }




    public delegate Task<Dictionary<string, string>> QueryRowDelegate(IPage page, IRow row, Dictionary<string, int> tableFields);
    public delegate void TaskFinishedHandler(TaskInformation ti);
    public delegate void TaskCancelHandler(TaskInformation ti);



}





